/**
 *   Copyright (c) fanguangping. All rights reserved.
 *   The use and distribution terms for this software are covered by the
 *   Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
 *   which can be found in the file epl-v10.html at the root of this distribution.
 *   By using this software in any fashion, you are agreeing to be bound by
 * 	 the terms of this license.
 *   You must not remove this notice, or any other, from this software.
 **/
package org.jfunny.core.sys.impl;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.jfunny.core.JFunny;
import org.jfunny.core.ds.Arg;
import org.jfunny.core.ds.Loop;
import org.jfunny.core.ds.Statement;
import org.jfunny.core.mapping.IMapping;
import org.jfunny.core.mapping.RegexMapping;
import org.jfunny.core.mapping.SimpleMapping;
import org.jfunny.core.meta.Namespace;
import org.jfunny.core.parser.ParserFunny;
import org.jfunny.core.sys.ISysEvaluator;
import org.jfunny.core.sys.SysLoader;
import org.jfunny.core.util.StringUtil;

/**
 * 系统函数的Clojure解释器
 * 
 * @author fanguangping
 *
 */
public class ClojureEvaluator implements ISysEvaluator {

	//global settings
	public static Namespace currentNamespace = null;

	@Override
	public Namespace getNamespace(String statement) {
		String funcName = getFunctionName(statement);
		String[] funcArgs = getFunctionArgs(statement);

		if (SYS_TPLSPACE.equals(funcName)) {
			String namespace = funcArgs[0];

			Namespace ns = new Namespace(namespace);
			ns.setTemplateSpace(true);
			return ns;
		}

		if (SYS_NAMESPACE.equals(funcName)) {
			String namespace = funcArgs[0];
			String includeTpls = funcArgs[1];
			String requireLibs = funcArgs[2];
			String useLibs = funcArgs[3];

			Set<Namespace> used = new HashSet<Namespace>();
			addToSet(used, includeTpls);
			addToSet(used, requireLibs);
			addToSet(used, useLibs);

			Namespace ns = new Namespace(namespace);
			ns.setTemplateSpace(false);
			ns.getUsed().addAll(used);
			return ns;
		}
		return null;
	}

	@Override
	public void prepare(String statement) {
		String funcName = getFunctionName(statement);
		String[] funcArgs = getFunctionArgs(statement);

		if (SYS_TPLSPACE.equals(funcName)) {
			String namespace = funcArgs[0];
			currentNamespace = Namespace.findNamespace(JFunny.allNamespace, namespace);
		}

		if (SYS_NAMESPACE.equals(funcName)) {
			String namespace = funcArgs[0];
			currentNamespace = Namespace.findNamespace(JFunny.allNamespace, namespace);
		}

		if (SYS_NS_MAPPING.equals(funcName)) {
			//do nothing
		}

		if (SYS_TEMPLATE.equals(funcName)) {
			String name = funcArgs[0];
			String key = funcArgs[1];
			String value = funcArgs[2];

			IMapping mapping = null;
			if (key.contains(Loop.PAREN_L)) {
				mapping = new RegexMapping(name, key, value);
			}
			else {
				mapping = new SimpleMapping(name, key, value);
			}
			addToMap(SysLoader.MAPPINGS, currentNamespace, mapping);
		}

		if (SYS_FUNCTION.equals(funcName)) {
			String name = currentNamespace.getName() + "/" + funcArgs[0];
			String key = funcArgs[1];
			String value = "(" + name + " " + getArgListWithParen(key) + ")";
			
			if (variableArgs(key)) {
				value = "(" + name + " " + getVariableArgListWithParen(key) + ")";
				key = getVariableKey(key);
				RegexMapping mapping = new RegexMapping(name, key, value);
				addToMap(SysLoader.MAPPINGS, currentNamespace, mapping);
			}
			else {
				SimpleMapping mapping = new SimpleMapping(name, key, value);
				addToMap(SysLoader.MAPPINGS, currentNamespace, mapping);
			}

			SysLoader.FUNCTIONS.add(name);
		}
	}

	@Override
	public String eval(String statement) {
		String funcName = getFunctionName(statement);
		String[] funcArgs = getFunctionArgs(statement);

		if (SYS_TPLSPACE.equals(funcName)) {
			//do nothing
		}

		if (SYS_NAMESPACE.equals(funcName)) {
			String namespace = funcArgs[0];
			//String includeTpls = funcArgs[1];
			String requireLibs = funcArgs[2];
			String useLibs = funcArgs[3];

			currentNamespace = Namespace.findNamespace(JFunny.allNamespace, namespace);

			StringBuffer buffer = new StringBuffer();
			buffer.append("(ns ");
			buffer.append(namespace);
			if (StringUtil.isNotBlank(requireLibs)) {
				buffer.append(" (:require ");
				buffer.append(requireLibs);
				buffer.append(")");
			}
			if (StringUtil.isNotBlank(useLibs)) {
				buffer.append(" (:use ");
				buffer.append(useLibs);
				buffer.append(")");
			}
			buffer.append(")");

			return buffer.toString();
		}

		if (SYS_NS_MAPPING.equals(funcName)) {
			String namespace = funcArgs[0];
			String expression = funcArgs[1];

			String valueExpr = null;
			try {
				valueExpr = JFunny.parse(Namespace.findNamespace(JFunny.usedNamespace, namespace), "{" + expression
						+ "}");
			}
			catch (Exception e) {
				e.printStackTrace();
			}

			String originalFunc = StringUtil.substringAfter(valueExpr, "(");
			originalFunc = originalFunc.trim();
			originalFunc = StringUtil.substringAfter(originalFunc, "/");

			StringBuffer buffer = new StringBuffer();
			buffer.append("(");
			buffer.append(namespace);
			buffer.append("/");
			buffer.append(originalFunc);

			return buffer.toString();
		}

		if (SYS_TEMPLATE.equals(funcName)) {
			//do nothing
		}

		if (SYS_FUNCTION.equals(funcName)) {
			String name = funcArgs[0];
			String key = funcArgs[1];
			String value = funcArgs[2];

			StringBuffer buffer = new StringBuffer();
			buffer.append("(defn ");
			buffer.append(name);
			buffer.append(" [");
			//buffer.append(getArgList(key));
			if (variableArgs(key)) {
				buffer.append(getVariableArgList(key));
			}
			else {
				buffer.append(getArgList(key));
			}
			buffer.append("] ");
			
			String valueExpr = null;
			try {
				String trimValue = value.trim();
				if (trimValue.startsWith("`") && trimValue.endsWith("`")) {
					valueExpr = trimValue.substring(2, trimValue.length() - 2);
				}
				else {
					valueExpr = JFunny.parse(null, "{" + trimValue + "}");
				}
			}
			catch (Exception e) {
				e.printStackTrace();
			}

			buffer.append(valueExpr.trim()); //匹配并翻译value

			buffer.append(")");

			return buffer.toString();
		}

		return "";
	}

	/**
	 * 
	 * @param function
	 * @return
	 */
	private static String getFunctionName(String function) {
		String funcName = StringUtil.substringAfter(function, "(");
		funcName = funcName.trim();
		funcName = StringUtil.substringBefore(funcName, " ");
		return funcName;
	}

	private static String[] getFunctionArgs(String function) {
		String funcArg = StringUtil.substringAfter(function, "(");
		funcArg = funcArg.trim();
		funcArg = StringUtil.substringBeforeLast(funcArg, ")");
		funcArg = funcArg.trim();
		funcArg = StringUtil.substringAfter(funcArg, DELIMITER);
		String[] result = funcArg.split(DELIMITER);

		for (int i = 0; i < result.length; i++) {
			String str = result[i].trim();
			String subStr = (str.startsWith("`")) ? str : str.substring(1, str.length() - 1);
			result[i] = subStr;
		}

		return result;
	}
	
	// statement例如：{a...}的和
	private static boolean variableArgs(String statement) {
		List<Statement> list = ParserFunny.parse("{" + statement + "}").getChildren();
		for (Statement s : list) {
			if (s instanceof Arg) {
				String argName = ((Arg) s).getContent();
				if (argName.endsWith("...")) {
					return true;
				}
			}
		}
		return false;
	}
	
	private static String getVariableKey(String statement) {
		String oldStr = null;
		String newStr = null;
		List<Statement> list = ParserFunny.parse("{" + statement + "}").getChildren();
		for (Statement s : list) {
			if (s instanceof Arg) {
				String argName = ((Arg) s).getContent();
				if (argName.endsWith("...")) {
					oldStr = "{" + argName +"}";
					newStr = "【{" + argName.substring(0, argName.length() - 3) + "}】*";
				}
			}
		}
		
		return statement.replace(oldStr, newStr);
	}
	
	private static String getVariableArgList(String statement) {
		StringBuffer buffer = new StringBuffer();
		String variableArg = null;

		List<Statement> list = ParserFunny.parse("{" + statement + "}").getChildren();
		for (Statement s : list) {
			if (s instanceof Arg) {
				String argName = ((Arg) s).getContent();
				if (argName.endsWith("...")) {
					variableArg = argName.substring(0, argName.length() - 3);
				}
				else {
					buffer.append(argName);
					buffer.append(" ");
				}
			}
		}
		buffer.append("& ");
		buffer.append(variableArg);
		
		return buffer.toString().trim();
	}
	
	private static String getVariableArgListWithParen(String statement) {
		StringBuffer buffer = new StringBuffer();
		String variableArg = null;

		List<Statement> list = ParserFunny.parse("{" + statement + "}").getChildren();
		for (Statement s : list) {
			if (s instanceof Arg) {
				String argName = ((Arg) s).getContent();
				if (argName.endsWith("...")) {
					variableArg = "{" + argName.substring(0, argName.length() - 3) + "}";
				}
				else {
					buffer.append("{" + argName + "}");
					buffer.append(" ");
				}
			}
		}
		buffer.append("【");
		buffer.append(variableArg);
		buffer.append("】*");
		
		return buffer.toString().trim();
	}

	// statement例如：{a}与{b}的平方和
	private static String getArgList(String statement) {
		StringBuffer buffer = new StringBuffer();

		List<Statement> list = ParserFunny.parse("{" + statement + "}").getChildren();
		for (Statement s : list) {
			if (s instanceof Arg) {
				String argName = ((Arg) s).getContent();
				buffer.append(argName);
				buffer.append(" ");
			}
		}

		return buffer.toString().trim();
	}

	private static String getArgListWithParen(String statement) {
		StringBuffer buffer = new StringBuffer();

		List<Statement> list = ParserFunny.parse("{" + statement + "}").getChildren();
		for (Statement s : list) {
			if (s instanceof Arg) {
				String argName = ((Arg) s).toString();
				buffer.append(argName);
				buffer.append(" ");
			}
		}

		return buffer.toString().trim();
	}

	private static void addToSet(Set<Namespace> set, String namespaces) {
		if (StringUtil.isNotBlank(namespaces)) {
			for (String namespace : StringUtil.split(namespaces)) {
				set.add(new Namespace(namespace));
			}
		}
	}

	private static void addToMap(Map<Namespace, List<IMapping>> map, Namespace namespace, IMapping mapping) {
		if (map.get(namespace) == null) {
			map.put(namespace, new ArrayList<IMapping>());
		}
		map.get(namespace).add(mapping);
	}
}
